#include "Image.h"

namespace sol9
{
namespace graphics
{

	__implementBaseClass(IImage);
	__implementClass(Image, IImage);

	Image::Image(eColorFormat eFormat, const core::dimension2du& nSize)
		: m_pData(nullptr), m_nSize(nSize), m_eFormat(eFormat)
		, m_nBytesPerPixel(0), m_nPitch(0), m_bLocked(false), m_bShared(false)
	{
		_initialize();
	}

	Image::Image(eColorFormat eFormat, const core::dimension2du& nSize, void* pData, bool bCopy)
		: m_pData(nullptr), m_nSize(nSize), m_eFormat(eFormat)
		, m_nBytesPerPixel(0), m_nPitch(0), m_bLocked(false), m_bShared(!bCopy)
	{
		if (m_bLocked)
			return;

		if (bCopy)
		{
			_initialize();

			/// Little Endian : Bottom-Top
			u8* pDstData = m_pData;
			u8* pSrcData = static_cast<u8*>(pData);

			pDstData = pDstData + m_nSize.height * m_nPitch;
			pDstData = pDstData - m_nPitch;

			for (int i = 0; i < (s32)m_nSize.height; ++i)
			{
				memcpy(pDstData, pSrcData, m_nPitch);

				pDstData = pDstData - m_nPitch;
				pSrcData = pSrcData + m_nPitch;
			}

			/// Big Endian : Top-Bottom
			//memcpy(m_pData, pData, getSizeInBytes());
		}
		else
		{
			m_pData = (u8*)pData;
			_initialize();
		}
	}

	Image::~Image()
	{
		if (m_pData && !m_bShared)
			delete[] m_pData;
	}

	void* Image::lock()
	{
		if (m_bLocked)
		{
			return nullptr;
		}
		else
		{
			m_bLocked = true;
			return m_pData;
		}
	}

	void Image::unlock()
	{
		m_bLocked = false;
	}

	void Image::blit(IImage* pOther, const core::position2di& Position, eBlitOption eOption)
	{
		if (!pOther)
			return;

		switch (eOption)
		{
		case EBO_ImageCopy :
			_blitImage(pOther, Position);
			break;

		case EBO_ImageBlend :
			_blendImage(pOther, Position);
			break;
		}
	}

	void Image::fill(const color& cColor)
	{
		switch (m_eFormat)
		{
		case ECF_R8G8B8 :
			{
				u8 c[3] = {cColor.r, cColor.g, cColor.b};
				
				for (u32 i = 0; i < getSizeInBytes(); i += 3)
				{
					memcpy(m_pData + i, c, 3);
				}

				return;
			}

		case ECF_A8R8G8B8 :
				memset(m_pData, cColor.argb, getSizeInBytes());
				return;

		default :
			return;
		}
	}

	color Image::getPixel(u32 x, u32 y) const
	{
		if (x >= m_nSize.width || y >= m_nSize.height)
			return color(0);

		switch (m_eFormat)
		{
		case ECF_R8G8B8 :
			{
				u8* t = m_pData + (y * m_nPitch) + (x * 3);
				return color(255, t[0], t[1], t[2]);
			}

		case ECF_A8R8G8B8 :
			{
				return ((u32*)m_pData)[y * m_nSize.width + x];
			}

		default :
			return color(0);
		}
	}

	void Image::setPixel(u32 x, u32 y, const color& c)
	{
		if (m_bLocked)
			return;

		if (x >= m_nSize.width || y >= m_nSize.height)
			return;

		switch (m_eFormat)
		{
		case ECF_R8G8B8 :
			{
				u8* dest = m_pData + (y * m_nPitch) + (x * 3);
				dest[0] = c.getRed();
				dest[1] = c.getGreen();
				dest[2] = c.getBlue();
			}
			break;

		case ECF_A8R8G8B8 :
			{
				u32* dest = (u32*)(m_pData + (y * m_nPitch) + (x * 4));
				*dest = c.argb;
			}
			break;
		}
	}

	const core::dimension2du& Image::getDimension() const
	{
		return m_nSize;
	}

	u32 Image::getBitsPerPixel() const
	{
		return getBitsPerPixelFromFormat(m_eFormat);
	}

	u32 Image::getBytesPerPixel() const
	{
		return m_nBytesPerPixel;
	}

	u32 Image::getSizeInBytes() const
	{
		// return m_nSize.getArea() * m_nBytesPerPixel;
		return m_nPitch * m_nSize.height;
	}

	u32 Image::getSizeInPixels() const
	{
		return m_nSize.getArea();
	}

	eColorFormat Image::getColorFormat() const
	{
		return m_eFormat;
	}

	bool Image::_initialize()
	{
		if (m_bLocked)
			return false;

		m_nBytesPerPixel = getBitsPerPixelFromFormat(m_eFormat) / 8;
		m_nPitch = m_nBytesPerPixel * m_nSize.width;

		if (!m_pData)
			m_pData = new u8[m_nSize.height * m_nPitch];

		return true;
	}

	void Image::_blitImage(IImage* pOther, const core::position2di& Position)
	{
		if (!pOther)
			return;

		const void* pSrc = pOther->lock();
		void* pDst = this->lock();

		if (pSrc && pDst)
		{
			/// if same format..
			if (this->getColorFormat() != pOther->getColorFormat())
				return;

			/// calculate pitch; width * pixel size
			u32 nSrcPitch = pOther->getBytesPerPixel() * pOther->getDimension().width;
			//u32 nDstPitch = this->getBytesPerPixel() * this->getDimension().width;
			u32 nDstPitch = this->m_nPitch;

			/// clipping both rectangle
			core::rect2di rtSrc = _clipImage(pOther, Position);
			u32 nClipPitch = rtSrc.getWidth() * pOther->getBytesPerPixel();

			/// if image position became negative number after clipping...
			/// save in temp variable.
			core::position2di posSrcBegin;
			posSrcBegin.x = std::min(Position.x, 0);
			posSrcBegin.y = std::min(Position.y, 0);

			/// calculate offset
			s32 nSrcOffset = posSrcBegin.y * nSrcPitch + posSrcBegin.x * getBytesPerPixel();
			s32 nDstOffset = rtSrc.top * nDstPitch + rtSrc.left * getBytesPerPixel();

			pSrc = (u8*)pSrc - nSrcOffset;
			pDst = (u8*)pDst + nDstOffset;

			/// blit image
			for (s32 y = rtSrc.top; y < (s32)rtSrc.bottom; ++y)
			{
				memcpy((u8*)pDst, pSrc, nClipPitch);

				pSrc = (u8*)pSrc + nSrcPitch;
				pDst = (u8*)pDst + nDstPitch;
			}
		}

		pOther->unlock();
		this->unlock();
	}

	void Image::_blendImage(IImage* pOther, const core::position2di& Position)
	{
	}

	core::rect2di Image::_clipImage(IImage* pOther, const core::position2di& Position)
	{
		core::rect2di rtDst(core::position2di(0), (core::dimension2di)m_nSize);
		core::rect2di rtSrc(Position, (core::dimension2di)pOther->getDimension());
		
		rtSrc.clipAgainst(rtDst);
		return rtSrc;
	}

} // namespace graphics
} // namespace sol9